package src;

import java.util.AbstractList;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

public class Persona {
	/*
	 * La persona tiene un mapa con sus preferencias donde se accede por el nombre de la persona, 
	 * y a su ves una lista ordenada por el indice de preferencia (que sera la posicion en la lista)
	 * 
	 */
	
	private class Preferencia{
		
		
		private Persona personaPreferencia;
		private int indicePreferencia;
		public Preferencia(Persona personaPreferencia,int indicePreferencia){
			this.personaPreferencia=personaPreferencia;
			this.indicePreferencia=indicePreferencia;
		}
	
		public Persona getPersonaPreferencia() {
			return personaPreferencia;
		}
	
		public int getIndicePreferencia(){
			return indicePreferencia;
			
		}
		
		
	}
	
	private String nombre;
	private Persona parejaActual;
	private AbstractMap<String,Preferencia> preferencias;
	private AbstractList<Persona> preferenciasOrdenadas;
	private int ultimaDeclaracion;
	private Persona personaSola;
	
	
	
	public Persona(String nombre){
		this.nombre=nombre;
		this.preferencias= new HashMap<String, Preferencia>();
		this.preferenciasOrdenadas=new ArrayList<Persona>();
		this.ultimaDeclaracion=0;
		this.parejaActual= null;
		this.personaSola=null;
	}
	
	/*
	 * Agrega la siguiente preferencia en el orden siguiente, cada agregado mediante este metodo 
	 * se tendr� menor preferencia que el anterior
	 */
	public void agregarPreferenciaSiguiente(Persona preferencia){
		this.preferenciasOrdenadas.add(preferencia);
		this.preferencias.put(preferencia.nombre, new Preferencia(preferencia,this.preferenciasOrdenadas.size()));
	}
	
	
	public int nivelPreferenciaParejaActual(){
		if(this.parejaActual==null)
			throw new NoTieneParejaExcepcion();
		return this.preferencias.get(this.parejaActual.nombre).getIndicePreferencia();
			
	}
	
	/*
	 * Devuelve true si al realizar la ultima declaracion se provoco un divorcio
	 */
	public boolean provocoDivorcios(){
		return this.personaSola!=null;
	}
	
	
	/*
	 * Devuelve la persona que quedo sola despues de habersele declarado al ultimo en su lista.
	 * Es decir si cuando el actual se le declara a otra persona, este ultimo deja su pareja y esta queda sola,
	 * el metodo devolvera esta ultima persona
	 */
	public Persona personaSola(){
		if(this.personaSola==null)
			throw new NoProvocoDivorciosExcepcion();
		return this.personaSola;
	}
	
	
	/*
	 * Devuelve un iterador sobre los preferidos, iterando de mayor a menor preferencia
	 */
	
	public  Iterator<Persona> obtenerIteradorPreferidos(){
		return  this.preferenciasOrdenadas.iterator();
		
	}

	public String darNombre() {
		return this.nombre;
	}
	
	public boolean seDeclaroAtodasLasPreferencias(){
		return this.ultimaDeclaracion   >= this.preferenciasOrdenadas.size();
		
	}
	
	/*
	 * Se declara a la siguiente preferencia en su lista y se establece la pareja si es de la preferencia de la otra persona
	 * 
	 */

	public void declararseASiguiente(){
		if(seDeclaroAtodasLasPreferencias())
			throw new FinDeLaListaDePreferenciasException();
		Persona nuevaPareja= this.preferenciasOrdenadas.get(this.ultimaDeclaracion++);
		if(nuevaPareja.esMejorQueActual(this)){
			this.casarseCon(nuevaPareja);
		}
	}
	
	public void casarseCon(Persona otraPersona) {
		if(this.enPareja()){
			this.separar();
		}
		if(otraPersona.enPareja()){
			otraPersona.separar();
			if(otraPersona.provocoDivorcios())
				personaSola=otraPersona.personaSola();
		}
		else
			personaSola= null;
		otraPersona.parejaActual=this;
		this.parejaActual=otraPersona;
		
	}

	public int nivelDePreferencia(Persona p){
		if(!this.preferencias.containsKey(p.nombre))
			throw new NoExistePersonaConNombreExcepcion();
		
		return this.preferencias.get(p.nombre).getIndicePreferencia();
	}
	
	public boolean esMejorQueActual(Persona otraPersona){
		if(this.parejaActual==null)
			return true;
		
		try{
			return (this.nivelPreferenciaParejaActual()>this.nivelDePreferencia(otraPersona));
		}
		catch(NoTieneParejaExcepcion e){
			return true;
		}
		
	}
	
	public boolean esEstableCon(Persona otraPersona){
		if(otraPersona==this.parejaActual)
			return true;
		if(this.esMejorQueActual(otraPersona) && otraPersona.esMejorQueActual(this))
			return false;
		return true;
		
	}
	
	public String getNombre(){
		return this.nombre;
	}
	
	public Persona getParejaActual(){
		if(this.parejaActual==null)
			throw new NoTieneParejaExcepcion();
		return this.parejaActual;
	}

	public boolean enPareja() {
		return this.parejaActual!=null;
	}
	
	public Persona getPreferenciaConNombre(String nombre){
		if(!this.preferencias.containsKey(nombre))
			throw new NoExistePersonaConNombreExcepcion();
		return this.preferencias.get(nombre).getPersonaPreferencia();
		
	}

	public void separar() {
		if(this.enPareja()){
			// cuando la persona se separe su pareja se separa de ella
			this.parejaActual.parejaActual=null;
			personaSola= this.parejaActual;
			this.parejaActual=null;
		}
	}
	
	public String toString(){
		return this.darNombre();
	}


}
